from common.db import models
from django.contrib.postgres.fields import JSONField
from common.exceptions import JsonAPIException
from common import int_2_2d, int_2_nd_generator
import copy
from decimal import Decimal
from django.utils.html import format_html
from apps.product.models import Product
# Create your models here.


class Repo(models.Model):
    """
    仓
    """
    class Meta:
        verbose_name = verbose_name_plural = '仓库'
    output_fields = ['id', 'name', 'address', 'no', 'price_per_100g_1d']

    init_data = [
        {'name': '昆明', 'address': '天池', 'no': 'KM', 'price_per_100g_1d': Decimal('0.01')}
    ]

    name = models.CharField("茶仓名字")
    address = models.TextField("茶仓地址")
    no = models.CharField("仓库码（地点缩写：KM）", blank=True, null=True)
    price_per_100g_1d = models.DecimalField("元/1g/每日", max_digits=8, decimal_places=4)

    def __str__(self):
        return self.name

class StoreUnit(models.Model):
    """Store Unit"""
    class Meta:
        ordering = ('add_timestamp', )
        verbose_name = verbose_name_plural = '存储单元'
    class ParentType(models.Choices):
        CROWD_BOX_V1 = "密集柜"
    class ParentTypeFieldLabel(models.Choices):
        CROWD_BOX_V1 = ["仓库编号", "仓库存放区", '密集柜编号', '密集柜或箱体(a/b)', "层号", "货架区", "饼号"]
    class ParentTypeFieldRange(models.Choices):
        CROWD_BOX_V1 = [
            range(0, 10),
            range(ord('A'), ord('D')+1),
            range(1, 27),
            range(ord('a'), ord('a')+1),
            range(1, 7),
            range(1, 21),
            range(0, 10)
        ]
    repo = models.ForeignKey(Repo, "仓库", blank=True, null=True)
    parent_type = models.CharField("父级类型", choices=ParentType.get_choices())
    no = models.CharField("存放序列号")
    fields = JSONField("编号字段", default=[])
    repo_no = models.CharField("完整的存储编号", unique=True)
    in_use = models.BooleanField("是否在使用中", default=False)
    locked_by_product_id = models.CharField(verbose_name='待入库产品ID', blank=True, null=True, default=None)
    locked_by_product_expire_at = models.BigIntegerField(verbose_name='被产品锁定过期时间', default=0)
    goods_block_id = models.CharField(verbose_name='GBID', default='')

    @classmethod
    def parse_store_unit_repo_no(cls, repo_no):
        """KM 0 A 01 a 01 01 0"""
        try:
            raw_repo_no = repo_no
            rno = raw_repo_no[0:2]  # 截取仓库编码
            repo_no = raw_repo_no[2:]
            field0 = repo_no[0]
            field1 = repo_no[1]
            field2 = repo_no[2:4]
            field3 = repo_no[4]
            field4 = repo_no[5:7]
            field5 = repo_no[7:9]
            field6 = repo_no[9]
            parent_type = field3
            int_fields = [int(field0), ord(field1), int(field2), ord(field3), int(field4), int(field5), int(field6)]
            fields_limit = getattr(cls.ParentTypeFieldRange, cls.ParentType.CROWD_BOX_V1.name).value
            for idx, k in enumerate(int_fields):
                if k not in fields_limit[idx]:
                    return None
            return {
                "repo": Repo.objects.get(no=rno),
                "parent_type": parent_type,
                "no": field6,
                "fields": [field0, field1, field2, field3, field4, field5, field6],
                "int_fields": int_fields,
                "repo_no": raw_repo_no,
                "in_use": False,
            }
        except:
            return None

    @classmethod
    def get_free_unit(cls, repo_id, parent_type):
        return cls.objects.filter(repo_id=repo_id, parent_type=parent_type, in_use=False).order_by('add_timestamp').first()

    @classmethod
    def str_fields_to_int(cls, str_fields):
        field0 = str_fields[0]
        field1 = str_fields[1]
        field2 = str_fields[2:4]
        field3 = str_fields[4]
        field4 = str_fields[5:7]
        field5 = str_fields[7:9]
        field6 = str_fields[9]
        return [int(field0), ord(field1), int(field2), ord(field3), int(field4), int(field5), int(field6)]


    @classmethod
    def get_next_int_fields(cls, int_fields, parent_type_code='a'):
        """
        :param int_fields: 从仓库编号到饼号，7个元素
        :return:
        """
        if int_fields is None:
            return cls.get_first_int_fields(parent_type_code=parent_type_code)
        parent_type_code = int_fields[3]
        if chr(parent_type_code) == 'a':
            parent_type = cls.ParentType.CROWD_BOX_V1.name
        elif chr(parent_type_code) == 'b':
            return None
        else:
            return None
        next_int_fields = copy.copy(int_fields)
        my_ranges = getattr(StoreUnit.ParentTypeFieldRange, parent_type).value
        for i in range(len(int_fields)-1, -1, -1):
            # print(int_fields, my_ranges, i)
            my_range = my_ranges[i]
            if int_fields[i] < my_range[-1]:
                next_int_fields[i] += 1
                if i < len(int_fields)-1:  # 修改后几位为第一个值
                    for k in range(i+1, len(int_fields)):
                        next_int_fields[k] = my_ranges[k][0]
                break
        # print(int_fields, next_int_fields)
        if next_int_fields == int_fields:
            return None
        else:
            return next_int_fields

    @classmethod
    def get_first_int_fields(cls, parent_type_code='a'):
        """parent_type_code: a-密集柜 b-箱体"""
        if parent_type_code == 'a':
            parent_type = cls.ParentType.CROWD_BOX_V1.name
        elif parent_type_code == 'b':
            parent_type = cls.ParentType.CHEST_V1.name
        else:
            return None

        return [e[0] for e in getattr(cls.ParentTypeFieldRange, parent_type).value]


    def get_int_fields(self):
        fields = copy.copy(self.fields)
        no = self.no
        fields.append(no)
        int_fields = [int(f) if f.isdigit() else ord(f) for f in fields]
        return int_fields

    def __str__(self):
        return self.repo_no

    def get_other_repo_free_unit(self, repo_id):
        if self.repo.pk == repo_id:
            return self
        return StoreUnit.objects.filter(repo_id=repo_id, parent_type=self.parent_type, in_use=False).first()


class StoreUnitThree(models.Model):
    class Meta:
        ordering = ('-repo_no', )
        verbose_name = verbose_name_plural = '存储单元Thr'

    class StoreSpec(models.Choices):
        CAKE = [Product.TeaCakeType.G200.name, Product.TeaCakeType.G375.name, Product.TeaCakeType.G500.name]
        SMALL = [Product.TeaCakeType.SMALL.name, Product.TeaCakeType.G50.name, Product.TeaCakeType.G250.name]

        @classmethod
        def get_store_spec(cls, repo_store_area):
            if repo_store_area in ['A', 'B', 'C', 'D']:
                return cls.CAKE.name
            else:
                return cls.SMALL.name

        @classmethod
        def get_valid_repo_store_areas(cls, key_name):
            if key_name == cls.CAKE.name:
                return ['A', 'B', 'C', 'D']
            elif key_name == cls.SMALL.name:
                return ['E', ]
            else:
                return []

        @classmethod
        def get_key_name_by_product_tea_cake_type(cls, tea_cake_type):
            if tea_cake_type in cls.CAKE.value:
                return cls.CAKE.name
            elif tea_cake_type in cls.SMALL.value:
                return cls.SMALL.name
            else:
                return None

    store_spec = models.CharField(verbose_name='存放规格', choices=StoreSpec.get_choices())
    repo = models.ForeignKey(Repo, verbose_name="仓库")
    repo_index = models.CharField(verbose_name='仓库编号', max_length=1)
    repo_store_area = models.CharField(verbose_name='仓库存放区', max_length=1)
    no = models.CharField("饼号")
    fields = JSONField("编号字段（除仓储地）", default=[])
    int_fields = JSONField('整数格式的编号字段', default=[])
    repo_no = models.CharField("完整的存储编号", unique=True)
    in_use = models.BooleanField("是否在使用中", default=False)
    locked_by_product_id = models.CharField(verbose_name='待入库产品ID', blank=True, null=True, default=None)
    locked_by_product_expire_at = models.BigIntegerField(verbose_name='被产品锁定过期时间', default=0)
    goods_block_id = models.CharField(verbose_name='GBID', default='')

    @classmethod
    def get_valid_repo_indexs(cls):
        return [1,]

    @classmethod
    def get_fields_range(cls, repo_index, repo_store_area):
        repo_index = str(repo_index)
        repo_store_area = str(repo_store_area)
        if len(repo_index) != 1 or len(repo_store_area) != 1:
            raise JsonAPIException(code='CALL_ERROR', msg='调用错误', status=500)

        field_repo_index_range = [int(repo_index), ]
        field_repo_index_bits_count = 1
        field_repo_index_cc = int_2_nd_generator(field_repo_index_bits_count)
        if repo_store_area in ['A', 'B']:
            field_repo_store_area_range = [ord(repo_store_area)]
            field_crowd_box_index_range_or_shelf_index_range = range(1, 27)
            field_layer_index_range = range(1, 7)
            field_shelf_area_range = range(1, 21)
            field_cake_index_range = range(0, 10)
            field_repo_store_area_bits_count = 1
            field_crowd_box_index_range_or_shelf_index_bits_count = 2
            field_layer_index_bits_count = 1
            field_shelf_area_bits_count = 2
            field_cake_index_bits_count = 1
            field_repo_store_area_cc = chr
            field_crowd_box_index_range_or_shelf_index_cc = int_2_nd_generator(field_crowd_box_index_range_or_shelf_index_bits_count)
            field_layer_index_cc = int_2_nd_generator(field_layer_index_bits_count)
            field_shelf_area_cc = int_2_nd_generator(field_shelf_area_bits_count)
            field_cake_index_cc = int_2_nd_generator(field_cake_index_bits_count)
        elif repo_store_area in ['C', 'D']:
            field_repo_store_area_range = [ord(repo_store_area)]
            field_crowd_box_index_range_or_shelf_index_range = range(1, 29)
            field_layer_index_range = range(1, 7)
            field_shelf_area_range = range(1, 21)
            field_cake_index_range = range(0, 10)
            field_repo_store_area_bits_count = 1
            field_crowd_box_index_range_or_shelf_index_bits_count = 2
            field_layer_index_bits_count = 1
            field_shelf_area_bits_count = 2
            field_cake_index_bits_count = 1
            field_repo_store_area_cc = chr
            field_crowd_box_index_range_or_shelf_index_cc = int_2_nd_generator(field_crowd_box_index_range_or_shelf_index_bits_count)
            field_layer_index_cc = int_2_nd_generator(field_layer_index_bits_count)
            field_shelf_area_cc = int_2_nd_generator(field_shelf_area_bits_count)
            field_cake_index_cc = int_2_nd_generator(field_cake_index_bits_count)
        elif repo_store_area in ['E', ]:
            field_repo_store_area_range = [ord(repo_store_area)]
            field_crowd_box_index_range_or_shelf_index_range = range(1, 11)
            field_layer_index_range = range(1, 4)
            field_shelf_area_range = range(ord('A'), ord('F')+1)
            field_cake_index_range = range(1, 151)
            field_repo_store_area_bits_count = 1
            field_crowd_box_index_range_or_shelf_index_bits_count = 2
            field_layer_index_bits_count = 1
            field_shelf_area_bits_count = 1
            field_cake_index_bits_count = 3
            field_repo_store_area_cc = chr
            field_crowd_box_index_range_or_shelf_index_cc = int_2_nd_generator(field_crowd_box_index_range_or_shelf_index_bits_count)
            field_layer_index_cc = int_2_nd_generator(field_layer_index_bits_count)
            field_shelf_area_cc = chr
            field_cake_index_cc = int_2_nd_generator(field_cake_index_bits_count)
        else:
            raise JsonAPIException(code='STORE_AREA_UNSPPORT', msg='存放区错误', status=500)
        return {
            'repo_index': {
                'range': list(field_repo_index_range),
                'bits_count': field_repo_index_bits_count,
                'converter_callable': field_repo_index_cc,
                'order': 0,
            },
            'repo_store_area': {
                'range': list(field_repo_store_area_range),
                'bits_count': field_repo_store_area_bits_count,
                'converter_callable': field_repo_store_area_cc,
                'order': 1,
            },
            'crowd_box_index_range_or_shelf_index': {
                'range': list(field_crowd_box_index_range_or_shelf_index_range),
                'bits_count': field_crowd_box_index_range_or_shelf_index_bits_count,
                'converter_callable': field_crowd_box_index_range_or_shelf_index_cc,
                'order': 2,
            },
            'layer_index': {
                'range': list(field_layer_index_range),
                'bits_count': field_layer_index_bits_count,
                'converter_callable': field_layer_index_cc,
                'order': 3,
            },
            'shelf_area': {
                'range': list(field_shelf_area_range),
                'bits_count': field_shelf_area_bits_count,
                'converter_callable': field_shelf_area_cc,
                'order': 4,
            },
            'cake_index': {
                'range': list(field_cake_index_range),
                'bits_count': field_cake_index_bits_count,
                'converter_callable': field_cake_index_cc,
                'order': 5,
            }
        }


    @classmethod
    def int_fields_to_str_fields(cls, int_fields, repo_index, repo_store_area):
        str_fields = copy.copy(int_fields)
        fields_range = cls.get_fields_range(repo_index, repo_store_area)
        for field_key in fields_range:
            field_range_info = fields_range[field_key]
            order = field_range_info['order']
            converter_callable = field_range_info['converter_callable']
            str_fields[order] = converter_callable(int_fields[order])
        return str_fields

    @classmethod
    def get_next_store_unit_info(cls, current_store_unit=None, repo_index=None, repo_store_area=None, repo_id=None):
        if current_store_unit:
            repo_index = current_store_unit.repo_index
            repo_store_area = current_store_unit.repo_store_area
            current_int_fields = current_store_unit.int_fields
        else:
            repo_index = repo_index
            repo_store_area = repo_store_area
            current_int_fields = None

        if repo_id is None:
            repo = Repo.objects.first()
        else:
            repo = Repo.objects.get(pk=repo_id)

        fields_range = cls.get_fields_range(repo_index, repo_store_area)
        # print(fields_range)
        if current_int_fields is None:
            next_int_fields = [None for _ in range(len(fields_range.keys()))]
            for field_key in fields_range:
                field_range_info = fields_range[field_key]
                next_int_fields[field_range_info['order']] = field_range_info['range'][0]
        else:
            next_int_fields = copy.copy(current_int_fields)  # [12, 10, 4, 1,]
            my_ranges_map = {v['order']: v['range'] for _, v in fields_range.items()}
            for i in range(len(current_int_fields)-1, -1, -1):
                # i 倒序： 6,5,4,3,2,1,0 ,从current_int_fields最后一个元素开始检查
                my_range = my_ranges_map[i]
                if current_int_fields[i] < my_range[-1]:  # 判断当前元素是否在区间里
                    next_int_fields[i] += 1  # 如果此判断成立，此元素+1
                    if i < len(current_int_fields) - 1:  # 如果此时i的值不是current_int_fields的最后一个索引， 则需要修改后几位为第一个值
                        for k in range(i + 1, len(current_int_fields)):
                            next_int_fields[k] = my_ranges_map[k][0]
                    break

        if next_int_fields == current_int_fields:  # 无新存储单元
            return None

        next_str_fields = cls.int_fields_to_str_fields(next_int_fields, repo_index, repo_store_area)
        return {
            "store_spec": cls.StoreSpec.get_store_spec(repo_store_area),
            "repo": repo,
            "repo_index": repo_index,
            "repo_store_area": repo_store_area,
            "no": next_str_fields[-1],
            "fields": next_str_fields,
            "int_fields": next_int_fields,
            "repo_no": repo.no + ''.join(next_str_fields),
            "in_use": False,
        }



